If you want to know how to send data to subroutines in your AppleScript scripts, then this example shows you how. It describes how to call subroutine handlers in a script, and send positional or labeled parameters to that subroutine.
The example does this by constructing AppleEvents which are sent to a script handler in a script using OSAExecuteEvent, OSADoEvent and AESend.
This example is derived from the MenuScripter application. MenuScripter was created to show developers how to use the Open Scripting Architecture, allowing users to change scripts associated with menu items. See 'About MenuScripter' below for more information.
How to use the Application
MenuScripter is basically a text editing application. However, you can also compile and execute the text in any of its document windows.
To use MenuScripter you must have the AppleScript system extension installed. You will find that many things won't work if the application is not called 'MenuScripter', this is due to having this name in many uncompiled scripts that it uses.
The scripts that show parameter passing in MenuScripter must be in the same folder as the MenuScripter application. These are the following scripts compiled using Script Editor:
'script shift'
'script datestring'
'script changecreator'
'script get/set selection'
Also the script application:
'script topseeturvee'
This version of MenuScripter includes four examples under the Script Menu of scripts that get passed parameters. They aren't really intended to be very useful, but hopefully they will give you ideas as to how you can adopt the methods into your application and the knowledge to implement these ideas. They are as follows:
Above-Below
This script takes two window names or numbers and arranges the windows so that one is below the other.
Date
This script takes two booleans for date and time and sets the selection of the front window to the resulting date string.
Topseeturvee
This script takes the selection of the front window and reverses the word order.
Save as Script Editor File
This invokes a 'Save As…' command then uses a script to change the resulting creator of the file.
Sending Parameters to Script Handlers
The OSAExecuteEvent and OSADoEvent routines of the Scripting Component allow developers to call user defined subroutines in a script using an AppleEvent. They also allow for a result to be returned. In fact the only difference between the two routines is that OSAExecuteEvent returns a script ID for the resulting script value (which can then be coerced to an AEDesc using OSACoerceToDesc), whereas OSADoEvent returns the result as an AppleEvent.
Command handlers, for application commands, in a script can be called in the same way that you'd expect to invoke them in an application. The difference being that you send the AppleEvent through an OSAExecuteEvent or OSADoEvent routine. So if your script included the following open handler:
on open names
--
end open
You could call this handler by sending an open AppleEvent with one parameter. If you needed to send more than one parameter you'd need to send all of the parameters in a list.
Using command handlers is restrictive when sending parameters to scripts. You only have a limited number of command handlers, and therefore, a limited number of ways to send parameters. You could end up with most of your scripting code extracting parameters from a list. It is much tidier to be able to send parameters in the same way that you can in AppleScript, as layed out by the subroutine definition.
To send the AppleEvent to a subroutine handler defined in your script you need to create an event whose class is kASAppleScriptSuite ('ascr') and has an event ID of kASSubroutineEvent ('psbr'). At the moment I'll assume that the script has been loaded by your application, and has been compiled. This means that the target application will be your application (I shall explain about targeting script applications later). Below is some C code that could create the event.
AppleEvent myAppleEvent;
AEDesc selfAddress;
OSErr err;
if (MakeSelfAddress(&selfAddress) == noErr)
err = AECreateAppleEvent(kASAppleScriptSuite,
kASSubroutineEvent,
&selfAddress,
kAutoGenerateReturnID,
kAnyTransactionID,
&myAppleEvent);
The subroutine you require from your script is identified by it's name. So the name is sent as a parameter under the keyASSubroutineName ('snam') keyword, it is sent as typeChar. So if our subroutine name is minimumValue, the parameter could be added with the code below.
Notice how the subroutine name is all in lowercase. This is true even if the subroutine your calling contains uppercase characters in it's name.
AppleScript allows two types of subroutines. Those with labeled parameters and those with positional parameters. The way parameters are placed in the AppleEvent is dependent upon the type of parameters your subroutine takes.
Positional parameters are parameters that must be given in a specific order, as defined by the subroutine. The much used minimumValue routine is an example of positional parameters.
on minimumValue(x, y)
--
end minimumValue
Positional parameters are placed in the AppleEvent as a list, under the keyDirectObject keyword. The code below could be used to add parameters for minimumValue to our AppleEvent.
Labeled parameter subroutines may have a direct parameter first, which is added using the keyDirectObject keyword. Then predefined paramenter labels, which are the prepositions defined in 'ASRegistry.h', e.g. keyASPrepositionAt. Lastly, user defined labels following the special label "given".
You send the user defined labels in a list containing pairs. The pairs consist of the label name as typeChar followed by the value. This list is then added to the AppleEvent under the keyASUserRecordFields ('usrf') keyword.
So creating a labeled subroutine with all types of parameters.
on belief of something instead of whatever given ¬
predicament:predicament, frequency:frequency
--
end belief
We would add the parameters to an AppleEvent for this subroutine as follows:
// Need subroutine name in lowercase - even if defined
Where the myScriptID parameter is the result of an earlier OSACompile. It is useful to use the kOSACompileIntoContext mode when compiling the script. This allows the script to contain handlers, properties and globals. The properties and globals can then be updated by the handlers in the script. When you have finished with the script you can save these changes using OSAStore.
If you don't NULL the reply before you call OSAExecuteEvent or OSADoEvent you will probably get crashes. You should also dispose of the reply even if your subroutine returns no value.
Sending Parameters to Script Applications
The same AppleEvent format is used to call subroutines in script applications. The only difference being the target of the AppleEvent and the use of AESend instead of OSADoEvent or OSAExecuteEvent.
You target the script application the same as you'd target any other application. The example code uses the Process Serial Number which it gets when it launches the script application.
Implementation Information
All the code I have written for this example is kept in 'MSAEScript.c'. Except for the initialisation and disposal and menu calls, which are in 'MSMain.c'. There is also an idle procedure in 'MSMain.c' for the AESend routine used to target the script application.
The four example scripts in the code show all the types of parameters you can send to an AppleScript subroutine handler. The routines that call these scripts are ExecuteScript1, ExecuteScript2, ExecuteScript3 and of course ExecuteScript4. ExecuteScript3 is the routine that targets a script application.
Three of the scripts are saved as compiled scripts using Script Editor. The other script is saved as a script application which stays open and never shows a startup screen. The compiled scripts are loaded and the script application is launched in the SetUpScripts function.
Each of the compiled scripts is saved in it's own file in the same directory as the application. You could save the scripts as resources in your application. Having it in the same directory makes it easier for you to play around with the scripts if you so wish.
Each script gets saved back to it's file in the CleanUpAEScripts function, using OSAStore, before quitting. This is so any properties that the scripts may contain will have they're updated values saved.
The purpose of this example is to show how to send parameters to scripts. If it was done in the true MenuScripter spirit each of the examples would have a script associated with it's menu item. This script could then use load script to load the script with your subroutine definition, then call that subroutine through a tell statement. However, if it was done in this way the creation of the AppleEvents would be handled by AppleScript and the example would be an example of doing something else.
The above paragraph describes the reason behind the GetSelection and SetSelection functions. As it is I have implemented them using scripts also - mainly because I didn't like the thought of creating the AppleEvent to do these things, AppleScript does this very nicely for me.
How Can You Use This Information?
Being able to send and receive information from scripts allows the informed user to do their own customisation of your application. This is great for all kinds of applications.
Take for example rule based information that changes regularily e.g. calculating taxes for different countries, rate cards for advertising, etc…
The list is endless. Imagine making the user script all of their own illogical rules.
Applications that rely on information from unknown sources can benefit. If your application relies on sending and recieving information from applications that don't adhere to a standard suite you could just use different scripts for interfacing between applications. Then you only need to update the script when things change.
Using script applications allows your application to off load tasks to easily modifiable solutions. A script application can even process the information in the background using it's idle handler, returning a result only when it's finished.
These are just some ideas. There are many, many more.
About MenuScripter
MenuScripter is a derivation of 7Edit, a factored text editing sample code application. It has been designed to show how to use the features of the OSA from within your application. As the name implies, many of the menu commands are implemented using scripts instead of hard coding. In many cases the hard coding will also be found within MSAppleEvents.c, but it does not get called if a script to do the same is found.
Scripts are stored in two representations :
'scpt' - a compiled script extracted from the AppleScript component with OSAStore and saved as a resource.
'SCPT' - a raw textual script.
Menu scripts are stored as resources with an ID equal to theMenuID * 32 + the item number within the menu. A script works for the whole menu if it has a resource id of 32 * theMenuID.
MenuScripter does not only allow the use a scripts to drive menus, it also allows them to be edited. To edit the script for a menu simply hold control and option down as you select a menu item, and you will be presented with a dialog allowing editing of the script.
Further Information
There is a good article in Develop Issue 18, 'Programming for Flexibility: The Open Scripting Architecture' by Paul G. Smith, describing the techniques of using scripts in an application. I recommend that you read this if you don't know about loading, compiling and executing scripts.
Changes from MenuScripter 3.0d9
The following changes have been made since MenuScripter 3.0d9, which is on the MacOS SDK CD:
- Full native PPC support. 3.0d9 was missing some UPPs.
- Added support of the 'contents' property for the selection. 'get the contents of the selection of window 1' will now return the text of the selection.
- Removed Publish and Subscribe support, since it was buggy and has never been properly fixed.
- Updated code for connecting to the Scripting component.
- Included projects for Metrowerks CodeWarrior 68K and PPC and Symantec C++ for Macintosh 68K and PPC.
The main code and 'aete' resource in MenuScripter are largely unchanged from 3.0d9. However, 7Edit, which MenuScripter derives from, is being updated to make it more consistent with the Scriptable Text Editor. MenuScripter will be updated to reflect these changes.